//@author A0097972J
package common;

import hirondelle.date4j.DateTime;

import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.TimeZone;

/**
 * Helper methods for date and time. Provides converting, formating, parsing of DateTime
 */
public class DateHelper {

	private List<SimpleDateFormat> _listDateFormat;
	//Error msg
	private final String DATE_START_ERROR = "\n\nYear too early. Supports up to the year before the current year.\n";
	private final String DATE_END_ERROR = "\n\nYear too far in the future. Supports up to 80 years from current year.\n";
	//Format with arguments
	private final String READABLE_FORMAT = "%1$s.%2$s.%3$s %4$s:%5$s";
	private final String DATETIME_FORMAT = "%1$s-%2$s-%3$s %4$s:%5$s";
	private final String PARSEABLE_FORMAT = "%1$s.%2$s.%3$s %4$s%5$s";
	//Date and time formats
	private final String DATE1 = "dd-MM-yy";
	private final String DATE2 = "dd/MM/yy";
	private final String DATE3 = "dd MMM yy";
	private final String DATE4 = "dd.MM.yy";
	private final String DATETIME1 = "dd-MM-yy HHmm";
	private final String DATETIME2 = "dd/MM/yy HHmm";
	private final String DATETIME3 = "dd MMM yy HHmm";
	private final String DATETIME4 = "dd.MM.yy HHmm";
	private final String TIME1 = "HHmm";
	//Keywords supports
	private final String KEYWORD_TOMORROW = "tmr";
	private final String KEYWORD_NEXT_WEEK = "next week";
	private final String KEYWORD_DAY = "day";
	private final String KEYWORD_HOUR = "hr";
	private final String KEYWORD_MINUTE = "min";
	//Misc.
	private final String DEADLINE_HOUR = "23";
	private final String DEADLINE_MINUTES = "59";
	private final String DOUBLE_ZERO = "00";
	private final String ZERO = "0";
	private final String VALUE_TOMORROW = "1";
	private final String VALUE_NEXT_WEEK ="7";

	
	/**
	 * Initialize _listOfDateFormat and add all supported time and date formats
	 */
	public DateHelper(){

		initDateFormats();
	}

	/**
	 * Get the current system date and time
	 * includes date, time HH:mm:ss
	 * @return
	 */
	public DateTime getCurrentDateTime(){

		return DateTime.now(TimeZone.getDefault());
	}

	/**
	 * Get the day from Calendar input
	 * @param tempCal
	 * @return formatted String day
	 */
	public String getDay(Calendar tempCal){

		String day = String.valueOf(tempCal.get(Calendar.DAY_OF_MONTH));
		int intDay = tempCal.get(Calendar.DAY_OF_MONTH);
		if(intDay<10){
			day = ZERO+day;
		}
		return day;
	}

	/**
	 * Get the day from DateTime input
	 * @param tempDateTime
	 * @return formatted String day
	 */
	public String getDay(DateTime tempDateTime){
		String day = String.valueOf(tempDateTime.getDay());
		int intDay = tempDateTime.getDay();
		if(intDay<10){
			day = ZERO+day;
		}
		return day;
	}

	/**
	 * Get the month from the Calendar input
	 * @param tempCal
	 * @return formatted String month
	 */
	public String getMonth(Calendar tempCal){
		String month = String.valueOf(tempCal.get(Calendar.MONTH) + 1); //Jan = 0
		int intMonth = tempCal.get(Calendar.MONTH);
		if(intMonth<10){
			month = ZERO+month;
		}
		return month;
	}

	/**
	 * Get the month from DateTime input
	 * @param tempDateTime
	 * @return formatted String month
	 */
	public String getMonth(DateTime tempDateTime){
		String month = String.valueOf(tempDateTime.getMonth()); //Jan = 0
		int intMonth = tempDateTime.getMonth();
		if(intMonth<10){
			month = ZERO+month;
		}
		return month;
	}

	/**
	 * Get the hour from Calendar input, if no input return "00"
	 * @param tempCal
	 * @return formatted String hour
	 */
	public String getHour(Calendar tempCal, boolean isDeadline){
		String hour = DOUBLE_ZERO;  
		int intHour = tempCal.get(Calendar.HOUR_OF_DAY);
		if(intHour != 0){
			hour = String.valueOf(intHour);
			if(intHour<10){
				hour = ZERO+hour;
			}
		}else{
			if(isDeadline){
				hour = DEADLINE_HOUR;
			}
		}
		return hour;
	}

	/**
	 * Get the hour from DateTime input, if no input return "00"
	 * @param tempDateTime
	 * @return formatted String hour
	 */
	public String getHour(DateTime tempDateTime, boolean isDeadline){
		String hour = DOUBLE_ZERO;  
		int intHour = tempDateTime.getHour();
		if(intHour != 0){
			hour = String.valueOf(intHour);
			if(intHour<10){
				hour = ZERO+intHour;
			}
		}else{
			if(isDeadline){
				hour = DEADLINE_HOUR;
			}
		}
		return hour;
	}

	/**
	 * Get the minutes from DateTime input, if no input return "00"
	 * If is deadline and hour input is not default, minutes set to "59".
	 * @param tempCal
	 * @return formatted String minutes
	 */
	public String getMinutes(Calendar tempCal, boolean isDeadline){
		String minutes = DOUBLE_ZERO;
		int intMinutes = tempCal.get(Calendar.MINUTE);

		if(intMinutes != 0){
			minutes = String.valueOf(intMinutes);
		}else{
			if(isDeadline){
				if(tempCal.get(Calendar.HOUR_OF_DAY) == 0){
					minutes = DEADLINE_MINUTES;
				}
			}
		}
		return minutes;
	}

	/**
	 * Get the minutes from DateTime input, if no input return "00".
	 * If is deadline and hour input is not default, minutes set to "59".
	 * @param tempDateTime
	 * @return formatted String minutes
	 */
	public String getMinutes(DateTime tempDateTime, boolean isDeadline){
		String minutes = DOUBLE_ZERO;

		int intMinutes = tempDateTime.getMinute();

		if(intMinutes != 0){
			minutes = String.valueOf(intMinutes);
		}else{
			if(isDeadline){
				if(tempDateTime.getHour() == 0){
					minutes = DEADLINE_MINUTES;
				}
			}
		}
		return minutes;
	}

	/**
	 * Compare parameter DateTime with current DateTime
	 * 
	 * @param dt
	 * @return return -1 if before current Date, 1 if after current Date
	 */
	public int compareToCurrentDateTime(DateTime dt){
		if (dt != null) {
			return dt.compareTo(getCurrentDateTime());
		} else {
			return 1;
		}
	}

	/**
	 * Examples of additional date formats supported. </br>
	 * 1. 01-01-2014</br>
	 * 2. 01/01/2014</br>
	 * 3. 01 JAN 2014</br>
	 * 4. 01.01.2014</br>
	 * 5. 01-01-2014 2359</br>
	 * 6. 01/01/2014 2359</br>
	 * 7. 01 JAN 2014 2359</br>
	 * 8. 01.01.2014 2359</br>
	 * 9. 2359 | 100 = 0100</br>
	 * */
	private void initDateFormats(){

		_listDateFormat = new ArrayList<SimpleDateFormat>();

		//Date only
		_listDateFormat.add(new SimpleDateFormat(DATE1));
		_listDateFormat.add(new SimpleDateFormat(DATE2));
		_listDateFormat.add(new SimpleDateFormat(DATE3));
		_listDateFormat.add(new SimpleDateFormat(DATE4));
		//Date and Time
		_listDateFormat.add(new SimpleDateFormat(DATETIME1));
		_listDateFormat.add(new SimpleDateFormat(DATETIME2));
		_listDateFormat.add(new SimpleDateFormat(DATETIME3));
		_listDateFormat.add(new SimpleDateFormat(DATETIME4));
		//Time only
		_listDateFormat.add(new SimpleDateFormat(TIME1));
	}

	/**
	 * Initialize calendar, Set year range and lenient to false for each parsing.
	 * @return Calendar
	 */
	private Calendar initCalendar(){

		Calendar cal = new GregorianCalendar();
		cal.add(Calendar.YEAR, -10);
		cal.setLenient(false);

		return cal;
	}

	/**
	 * Check if the string date input can be converted to supported date formats.
	 * and return Calendar object
	 * 
	 * @param textInput
	 * @return the date type. Null if cannot convert
	 */
	public Calendar DateFormatter(String textInput) {

		Calendar cal = new GregorianCalendar();

		for(SimpleDateFormat sf: _listDateFormat){
			try {
				cal = initCalendar();

				sf.setLenient(false);
				sf.set2DigitYearStart(cal.getTime());

				if(sf.toPattern().equals(TIME1)){
					textInput = checkAndConvertTimeForParsing(textInput);
					sf = new SimpleDateFormat(DATETIME4);
				}

				ParsePosition position = new ParsePosition(0);
				Date dateParsed = sf.parse(textInput, position);
				
				if (position.getIndex() != textInput.length()) {
					cal = null;
				}
				else{
					cal.setTime(dateParsed);
				}
			} catch (Exception e) {
				return null;
			}
			if (cal != null) {
				return cal;
			}
		}
		return null;
	}

	/**
	 * Take in String time input and convert to parseable format with current date.
	 * @param input
	 * @return
	 */
	private String checkAndConvertTimeForParsing(String input){

		String timeString = checkAndFormatTimeString(input);
		if(timeString == null){
			return null;
		}

		input = convertCurrentDateTimeToParseableFormat(timeString);

		if(input == null){
			return null;
		}

		return input;
	}

	/**
	 * Take in string time input HHmm, return string in SimpleDateFormat "dd.MM.yy HHmm"
	 * @param input
	 * @return String, null if cannot convert
	 */
	private String convertCurrentDateTimeToParseableFormat(String input){
		try{
			String day = String.valueOf(getCurrentDateTime().getDay());
			String month = String.valueOf(getCurrentDateTime().getMonth());
			String year = String.valueOf(getCurrentDateTime().getYear());
			String hour = input.substring(0,2);
			String minutes = input.substring(2,4);

			return String.format(PARSEABLE_FORMAT, day, month, year, hour, minutes);

		}catch(NullPointerException e){
			return null;
		}
	}
	

	/**
	 * Check time HHmm. If not in 24hr format or cannot convert to integer, return null. 
	 * Else return string in 24hr format, HHmm.
	 * @param input
	 * @return
	 */
	private String checkAndFormatTimeString(String input){

		int time = convertToInteger(input);
		if(time != -1){
		
		if(time >2359){
			return null;
		}else{
			int lastTwoDigits = time % 100;

			if(lastTwoDigits >59){
				return null;
			}else{
				if(time <1000 && time >=100){
					return ZERO+time;
				}
			}
		}
		}
		return input;
	}

	/**
	 * Format the values in calendar to "YYYY-mm-DD HH:mm" String that supports DateTime format
	 * @param cal
	 * @param isEnd
	 * @return String
	 */
	public String formatCalendarToDateTimeString(Calendar cal, boolean isEnd){

		String year = String.valueOf(cal.get(Calendar.YEAR));
		String month = getMonth(cal);
		String day = getDay(cal);
		String hour = getHour(cal, isEnd);
		String minutes = getMinutes(cal, isEnd);

		return String.format(DATETIME_FORMAT, year, month, day, hour, minutes);
	}

	/**
	 * Format dateTime to readable format "DD.mm.YYYY HH:mm"
	 * @param dt
	 * @param isEndingTime
	 * @return String
	 */
	public String formatDateTimeToReadableString(DateTime dt, boolean isEndingTime){

		String day = getDay(dt);	
		String month = getMonth(dt);
		String year = String.valueOf(dt.getYear());
		String hour = getHour(dt,isEndingTime);
		String minutes = getMinutes(dt,isEndingTime);

		return String.format(READABLE_FORMAT, day, month, year, hour, minutes);
	}

	/**
	 * Format DateTime input to String that supports DateTime format
	 * @param dt
	 * @param isEndingTime
	 * @return String
	 */
	public String formatStringToDateTimeString(DateTime dt, boolean isEndingTime){

		String hour = DOUBLE_ZERO;
		String minutes = DOUBLE_ZERO;

		String day = getDay(dt);	
		String month = getMonth(dt);
		String year = String.valueOf(dt.getYear());

		if(isEndingTime){
			hour = DEADLINE_HOUR;
			minutes = DEADLINE_MINUTES;
		}
		return String.format(DATETIME_FORMAT, year, month, day, hour, minutes);
	}

	/** 
	 * Check if input year is between currentYear-1 and currentYear+80
	 * @param inputYear
	 * @return false if not in range
	 */
	public String checkSupportingYear(int inputYear){

		int currentYear = Integer.parseInt(getCurrentDateTime().format("YYYY"));

		if(inputYear < (currentYear-1)){
			return DATE_START_ERROR;
		}
		if(inputYear > (currentYear+80)){
			return DATE_END_ERROR;
		}
		return null;
	}

	/**
	 * Convert string input to DateTime
	 * @param input
	 * @return DateTime, null if error converting
	 */
	public DateTime convertStringToDateTime(String input){

		try{
			DateTime dt = new DateTime(input);
			return dt ;
		}catch(Exception e){
			return null;
		}
	}

	/**
	 * Parse the input and convert to integer
	 * @param input
	 * @return positive int if pass, -1 if fails
	 */
	public int convertToInteger(String input){
		int num = -1;

		input = input.trim();
		try{
			num = Integer.parseInt(input);
		}catch(NumberFormatException e){
			return -1;
		}
		return num;
	}

	/**
	 * Get the ending DateTime by adding the no. of days to the start date.
	 * @param start
	 * @param input
	 * @return DateTime
	 */
	public DateTime getPlusDateTime(DateTime start, String input){
		int num = convertToInteger(input);
		if(num < 0){
			return null;
		}
		String tempDate = formatStringToDateTimeString(start.plusDays(num), true);
		return convertStringToDateTime(tempDate);
	}

	/**
	 * Add days to current time based on keyword and return Datetime
	 * @param input
	 * @return
	 */
	public DateTime alternateKeywordParser(String input){

		switch(input){
		case KEYWORD_TOMORROW:
			return getPlusDateTime(getCurrentDateTime(), VALUE_TOMORROW);
		case KEYWORD_NEXT_WEEK:
			return getPlusDateTime(getCurrentDateTime(), VALUE_NEXT_WEEK);
		default:
			return null;
		}
	}

	/**
	 * Match the keyword to the no. of minutes in one unit.
	 * Eg. 1 hour = 60 minutes
	 * @param keyword
	 * @param input
	 * @return int
	 */
	public int totalMinutesConverted(String keyword, String input)
	{
		int temp = 0;

		if(keyword.equals(KEYWORD_HOUR)){
			temp = convertToMinutes(input, 60); //60
		}
		if(keyword.equals(KEYWORD_MINUTE)){
			temp = convertToMinutes(input, 1); //1
		}
		if(keyword.equals(KEYWORD_DAY)){
			temp = convertToMinutes(input, 1440); //1440
		}
		return temp;
	}
	
	/**
	 * Determine the keyword in the input and get the sum of the value.
	 * @param temp
	 * @return sum
	 */
	public int determineKeyword(String[] temp){
		int sum =0;
		try{
			for(int i=0; i<temp.length; i++){

				if(temp[i].equals(KEYWORD_HOUR) || temp[i].equals(KEYWORD_MINUTE) || temp[i].equals(KEYWORD_DAY)){
					sum+=totalMinutesConverted(temp[i].trim(), temp[i-1]);
				}
			}
		}catch(NullPointerException e){
			return -1;
		}
		return sum;
	}

	/**
	 * Parse input string to integer and multiply with multipler value
	 * @param input
	 * @param multipler
	 * @return int
	 */
	public int convertToMinutes(String input, int multipler){

		int temp = 0;

		try{
			temp  = Integer.parseInt(input);
			temp = temp * multipler;
		}catch(NumberFormatException e){
			return -1;
		}
		return temp;
	}

	/**
	 * format to DateTime string and convert
	 * @param tempCal
	 * @param isEnd
	 * @return the converted DateTime object
	 */
	public DateTime processGetDateTime(Calendar tempCal, boolean isEnd){

		String dateString = formatCalendarToDateTimeString(tempCal, isEnd);
		return convertStringToDateTime(dateString);

	}
}
